package com.vordel.gateway.viewPolicy;

import java.io.ByteArrayInputStream;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.vordel.circuit.Circuit;
import com.vordel.circuit.CircuitAbortException;
import com.vordel.circuit.CircuitInvocation;
import com.vordel.circuit.FilterConfigureContext;
import com.vordel.circuit.Message;
import com.vordel.circuit.MessageProcessor;
import com.vordel.circuit.MessageProperties;
import com.vordel.dwe.ByteArrayContentSource;
import com.vordel.es.ESPK;
import com.vordel.es.Entity;
import com.vordel.es.EntityStore;
import com.vordel.es.EntityStoreException;
import com.vordel.es.EntityType;
import com.vordel.es.Field;
import com.vordel.es.FieldType;
import com.vordel.es.UnknownTypeException;
import com.vordel.es.Value;
import com.vordel.es.fed.FederatedEntityStore;
import com.vordel.es.util.ShorthandKeyFinder;
import com.vordel.es.xes.PortableESPK;
import com.vordel.es.xes.PortableESPKFactory;
import com.vordel.mime.Body;
import com.vordel.mime.HeaderSet;
import com.vordel.mime.XMLBody;
import com.vordel.trace.Trace;

/**
 * This object acts as a viewPolicy "Addition" Web Service The processor will
 * examine the incoming XML message and will look for two parameters in the
 * message and then will add the two values togetther and set the message to be
 * the result of the calculation
 * 
 * The incoming message is expected to be in the following format:
 * 
 * <?xml version="1.0" encoding="utf-8"?> <soap:Envelope
 * xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 * xmlns:xsd="http://www.w3.org/2001/XMLSchema"
 * xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <Add
 * xmlns="http://startvbdotnet.com/web/"> <a>1</a> <b>1</b> </Add> </soap:Body>
 * </soap:Envelope>
 * 
 * The SimpleFilter will contain the local name of the two parameters (i.e. a
 * and b) and will also contain the namespace that these elements belong to
 * (i.e. http://startvbdotnet.com/web/)
 */
public class Processor extends MessageProcessor {

	private EntityStore es;
	private ShorthandKeyFinder skf;
	private Map<String,List<ESPK>> indecies = new HashMap<String,List<ESPK>>();
	
	// fragment of the hardcoded response for client (start of the XML response)
	private static final String RESPONSE_START = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";

	// fragment of the hardcoded response for client (end of the XML response)
	private static final String RESPONSE_END = "";

	@Override
	public void filterAttached(FilterConfigureContext ctx,
			com.vordel.es.Entity entity) throws EntityStoreException {
		// nothing to do here for initialisation
		super.filterAttached(ctx, entity);

		this.es = ctx.store;
		this.skf = ctx.skf;

		Trace.info("SKF=" + this.skf);
		
		buildIndicies(es.getRootPK());
		
	}

	
	private void buildIndicies(ESPK current) throws EntityStoreException {
		
		
		Collection<ESPK> children = this.es.listChildren(current, es.getBaseType());
		
		for (ESPK child: children) {
			
			Entity childEntity = es.getEntity(child);
			EntityType childEntityType = childEntity.getType();
			
			while (!childEntityType.equals(es.getBaseType())) {
				
				List<ESPK> index = this.indecies.get(childEntityType.toString());
				
				if (index == null) {
					index = new ArrayList<ESPK>();
				}
				
				index.add(child);
				
				indecies.put(childEntityType.toString(), index);
				
				childEntityType = childEntityType.getSuperType();
			}
			
			buildIndicies(child);
			
		}
	}

	/**
	 * The invoke method. This method performs the filter processing.
	 * 
	 * @param c
	 *            The circuit
	 * @param message
	 *            The message
	 * @return true or false.
	 */
	public boolean invoke(CircuitInvocation c, Message message)
			throws CircuitAbortException {

		// get the incoming request message as a DOM
		// Document doc = getDOM(message);

		// default result
		String result = "UNKNOWN";

		// cast the filter member variable to the SiteMinderFilter so that we
		// may get access to the some details store here, i.e parameter names
		// etc.
		Filter f = (Filter) getFilter();

		Trace.info("Keys=>" + message.keySet());
		// Trace.info("http.path.1=>"+message.get("http.path.1"));
		Trace.info("http.path.2=>" + message.get("http.path.2"));
		Trace.info("http.path.3=>" + message.get("http.path.3"));
		Trace.info("http.path.4=>" + message.get("http.path.4"));

		String contentType = (String) message.get("http.path.3");
		Trace.info("The content type is " + contentType);

		if (message.get("http.path.4")!=null && message.get("http.path.4").toString().equals("index")) {
			try {
				result = getIndex(c, message);
			} catch (Exception e) {
				return false;
			}
		} else {

			//This is not an index, its a regular call
			int i = 4;

			String path = "";
			String entityType = "";
			while (message.get("http.path." + i) != null) {

				Trace.info("http.path." + i + " is not null");

				if (i % 2 == 1) {
					// Even entries are the values of the object
					Trace.info("i=" + i + " is even");

					String pathElement = message.get("http.path." + i)
							.toString();

					path = path + "/[" + entityType + "]"
							+ URLDecoder.decode(pathElement);

					entityType = "";

				} else {
					// Odd Entries are entity types
					entityType = message.get("http.path." + i).toString();
				}

				Trace.info("PATH=>" + path);
				Trace.info("EntityType=>" + entityType);

				i++;
			}
			Trace.info("http.path." + i + " is NULL value=>"
					+ message.get("http.path." + i));

			ESPK espk = null;

			if (path.equals("")) {

				espk = es.getRootPK();

			} else {

				try {
					Entity e = this.skf.getEntity(path);
					if (e == null) {
						Trace.info("No Entity at " + path);
					} else {
						espk = e.getPK();
					}
				} catch (EntityStoreException e) {
					Trace.error(e);
				}

			}

			Trace.info("ESPK=" + espk);
			try {
				if (entityType.equals("")) {
					result = this.getObject(espk);
				} else {
					result = this.getChildern(espk, entityType);
				}

			} catch (EntityStoreException e) {
				e.printStackTrace();
			}
		}
		
		StringBuffer response = new StringBuffer(RESPONSE_START);
		response.append(result);
		response.append(RESPONSE_END);
		Body convertedBody = Body.create(new HeaderSet(),
				Body.CT_TEXT_XML_UTF8, new ByteArrayContentSource(response
						.toString().getBytes()));
		message.put(MessageProperties.CONTENT_BODY, convertedBody);

		return true;
	}

	private String getIndex(CircuitInvocation c, Message message)
			throws EntityStoreException {
		// TODO Auto-generated method stub

		String entityTypeName = (String) message.get("http.path.5");

		Trace.info("Building an index for " + entityTypeName);

		EntityType entityType = es.getTypeForName(entityTypeName);

		List<ESPK> entitiesOfType = //this.findAllEntitiesOfType(entityType);
				this.indecies.get(entityTypeName);
		
		String result = "<Index type=\""+entityTypeName+"\">";
		for (ESPK item: entitiesOfType) {
			result += "<Entity "+getEntityAttributes(item)+"/>";
		}
		result += "</Index>";
		return result;
	}

	
	private String getObject(ESPK espk) throws EntityStoreException {
		// TODO Auto-generated method stub

		// TODO Auto-generated method stub
		String result = "";

		Entity entity = es.getEntity(espk);

		PortableESPKFactory pESPKFactory = PortableESPKFactory.newInstance();
		PortableESPK pESPK = pESPKFactory
				.createPortableESPK(es, entity.getPK());

		result += "<Entity " + getEntityAttributes(espk) + ">";

		Field[] fields = entity.getAllFields();

		for (int i = 0; i < fields.length; i++) {

			if (fields[i].getType().isSoftRefType()) {

				Trace.info("SOFT REF=" + fields[i].getValueList());

				Value refValue = fields[i].getValueList().get(0);

				Trace.info(refValue.getClass().getName() + " "
						+ refValue.toString());

				result += "<Field name=\""
						+ fields[i].getName()
						+ "\" "
						+ getEntityAttributes(pESPKFactory
								.createPortableESPK(refValue.toString()))
						+ "/>";

			} else if (fields[i].getType().isRefType()) {

				ESPK ref = fields[i].getReference();

				Trace.info("Resolving reference " + fields[i].getName()
						+ " values=" + fields[i].getValueList() + " ref=" + ref);

				result += "<Field name=\"" + fields[i].getName() + "\" "
						+ getEntityAttributes(ref) + "/>";

			} else {

				result += "<Field name=\"" + fields[i].getName()
						+ "\" values=\""
						+ xmlEncode(fields[i].getValueList().toString())
						+ "\"/>";
			}

		}

		Trace.info("Declared Components="
				+ entity.getType().getDeclaredComponentTypes());

		Set<String> childTypes = entity.getType().getDeclaredComponentTypes()
				.keySet();

		for (String childType : childTypes) {

			result += "<ChildType name=\"" + childType + "\"/>";

		}

		Collection<ESPK> refs = es.findReferringEntities(entity.getPK());

		for (ESPK ref : refs) {

			result += "<ReferredBy " + getEntityAttributes(ref) + "/>";

		}
		
		result += buildHierary(espk);

		result += "</Entity>";

		return result;

	}

	private String buildHierary(ESPK espk) throws EntityStoreException {
		// TODO Auto-generated method stub
		String result = "<Hierarchy>";
		
		Stack<ESPK> ancestors = new Stack<ESPK>();
		
		Entity entity = this.es.getEntity(espk);
		
		while (!entity.getPK().equals(es.getRootPK())) {
			ancestors.push(entity.getPK());
			entity = es.getEntity(entity.getParentPK());
		}
		
		while (ancestors.size()>1) {
			
			ESPK ancestorESPK = ancestors.pop();
			result += "<Entity "+this.getEntityAttributes(ancestorESPK)+"/>";
		}
		
		result += "</Hierarchy>";
		
		return result;
	}


	private String pathOf(PortableESPK pESPK) {
		// TODO Auto-generated method stub
		String path = pESPK.toShorthandString();
		path = path.replace("[", "");
		path = path.replace("]", "/");
		path = URLEncoder.encode(path);
		path = path.replace("%2F", "/");
		// These are paths that were \ escaped
		path = path.replace("%5C/", "%5C%2F");
		return path;

	}

	private String getChildern(ESPK espk, String entityType)
			throws EntityStoreException {

		// TODO Auto-generated method stub
		String result = "<Entity " + getEntityAttributes(espk) + ">";

		result += "<Children type=\"" + entityType + "\">";
		Collection<ESPK> children = es.listChildren(espk,
				es.getTypeForName(entityType));

		for (ESPK child : children) {

			result += "<Entity " + getEntityAttributes(child) + "/>";

		}

		result += "</Children>";
		
		result += buildHierary(espk);
		
		result += "</Entity>";

		return result;
	}

	private String getEntityAttributes(ESPK child) throws EntityStoreException {

		PortableESPKFactory pESPKFactory = PortableESPKFactory.newInstance();
		// TODO Auto-generated method stub
		String result = "";

		if (child.equals(FederatedEntityStore.ES_NULL_PK)) {

			result += "type=\"UNDEFINED\" path=\"UNDEFINED\" keyFieldValue=\"UNDEFINED\"";
			return result;

		}

		Entity entity = es.getEntity(child);
		String keyFieldValue = getKeyFieldValue(entity);
		PortableESPK pESPK = pESPKFactory
			.createPortableESPK(es, entity.getPK());

		
		if (entity.getParentPK()!=null && !entity.getParentPK().equals(FederatedEntityStore.ES_NULL_PK)) {
		
			Entity parentEntity = es.getEntity(entity.getParentPK());
			String parentKeyFieldValue = getKeyFieldValue(parentEntity);

			
			PortableESPK parentpESPK = pESPKFactory.createPortableESPK(es, parentEntity.getPK());

			result += "type=\"" + entity.getType().getName() + "\" path=\""
				+ pathOf(pESPK) + "\" keyFieldValue=\"" + keyFieldValue + "\"" 
				+ " parentPath=\""+pathOf(parentpESPK) + "\" parentFieldValue=\"" + parentKeyFieldValue + "\"";
			
		} else {
			
			result += "type=\"" + entity.getType().getName() + "\" path=\""
			+ pathOf(pESPK) + "\" keyFieldValue=\"" + keyFieldValue + "\"" 
			+ " parentPath=\"UNDEFINED\" parentFieldValue=\"UNDEFINED\"";
		}

		return result;
	}

	private String getKeyFieldValue(Entity entity) {
		// TODO Auto-generated method stub
		

		EntityType type = entity.getType();

		String keyFieldValue = "";

		for (String keyField : Arrays.asList(type.getKeyFieldNames())) {

			String newValue = xmlEncode((String) entity.getField(keyField)
					.getValues()[0].toString());

			keyFieldValue += newValue;
			keyFieldValue += ",";
		}

		keyFieldValue = keyFieldValue.substring(0, keyFieldValue.length() - 1);
		
		return keyFieldValue;
	}


	private String xmlEncode(String string) {
		// TODO Auto-generated method stub
		String newValue = string;
		newValue = newValue.replace("'", "&quot;");
		newValue = newValue.replace("\"", "&quot;");
		newValue = newValue.replace("<", "&lt;");
		newValue = newValue.replace(">", "&gt;");
		return newValue;
	}

	/**
	 * Utility for get the DOM from the message
	 */
	protected Document getDOM(Message message) throws CircuitAbortException {
		try {
			return XMLBody.getDocument(message);
		} catch (java.io.IOException e) {
			throw new CircuitAbortException(e);
		}
	}

	/**
	 * Utility for getting the actual content of an element
	 */
	public static String getElementContent(Element element) {
		StringBuffer content = new StringBuffer();
		NodeList children = element.getChildNodes();
		for (int i = 0; i < children.getLength(); i++) {
			Node node = children.item(i);
			if (node.getNodeType() == Node.TEXT_NODE) {
				content.append(node.getNodeValue());
			}
		}
		return content.toString().trim();
	}
}
